package com.example.examsystem.service;

import com.example.examsystem.dto.PageResponse;
import com.example.examsystem.dto.UserDto;
import com.example.examsystem.exception.BusinessException;
import com.example.examsystem.model.Role;
import com.example.examsystem.model.User;
import com.example.examsystem.model.UserRole;
import com.example.examsystem.repository.RoleRepository;
import com.example.examsystem.repository.UserRepository;
import com.example.examsystem.repository.UserRoleRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 用户管理服务类
 * 提供用户的业务逻辑处理，包括用户增删改查、状态管理、角色分配等
 * 
 * @author system
 * @since 1.0.0
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    private final RoleRepository roleRepository;
    private final UserRoleRepository userRoleRepository;
    private final PasswordEncoder passwordEncoder;

    /**
     * 分页查询用户列表
     * 支持按用户名、邮箱、启用状态进行筛选
     * 
     * @param page 页码（从0开始）
     * @param size 每页大小
     * @param username 用户名（模糊查询，可为空）
     * @param email 邮箱（模糊查询，可为空）
     * @param enabled 启用状态（可为空）
     * @return 分页用户数据
     */
    public PageResponse<UserDto> getUsers(int page, int size, String username, String email, Boolean enabled) {
        log.debug("查询用户列表 - 页码: {}, 大小: {}, 筛选条件: 用户名={}, 邮箱={}, 状态={}", 
                page, size, username, email, enabled);
        
        // 创建分页对象，按创建时间倒序排列
        Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
        Page<User> userPage = userRepository.findByQuery(username, email, enabled, pageable);
        
        // 转换为DTO
        List<UserDto> content = userPage.getContent().stream()
            .map(this::convertToDto)
            .collect(Collectors.toList());
        
        log.debug("查询到用户数量: {}", content.size());
        
        return new PageResponse<>(
            content,
            userPage.getNumber(),
            userPage.getSize(),
            userPage.getTotalElements(),
            userPage.getTotalPages()
        );
    }

    /**
     * 根据ID获取用户详情
     * 
     * @param id 用户ID
     * @return 用户详情
     * @throws BusinessException 用户不存在时抛出
     */
    public UserDto getUserById(Long id) {
        log.debug("获取用户详情 - ID: {}", id);
        User user = userRepository.findById(id)
            .orElseThrow(() -> new BusinessException("用户不存在"));
        return convertToDto(user);
    }

    /**
     * 创建新用户
     * 会检查用户名和邮箱的唯一性，密码会进行加密处理
     * 
     * @param userDto 用户信息
     * @return 创建的用户信息
     * @throws BusinessException 用户名或邮箱已存在时抛出
     */
    @Transactional
    public UserDto createUser(UserDto userDto) {
        log.info("开始创建用户 - 用户名: {}", userDto.getUsername());
        
        // 检查用户名是否已存在
        if (userRepository.existsByUsername(userDto.getUsername())) {
            throw new BusinessException("用户名已存在");
        }
        
        // 检查邮箱是否已被使用
        if (userRepository.existsByEmail(userDto.getEmail())) {
            throw new BusinessException("邮箱已被使用");
        }
        
        // 创建用户实体
        User user = new User();
        user.setUsername(userDto.getUsername());
        user.setPassword(passwordEncoder.encode(userDto.getPassword())); // 密码加密
        user.setEmail(userDto.getEmail());
        user.setFullName(userDto.getFullName());
        user.setPhoneNumber(userDto.getPhoneNumber());
        user.setEnabled(userDto.getEnabled() != null ? userDto.getEnabled() : true); // 默认启用
        
        User savedUser = userRepository.save(user);
        log.info("用户创建成功 - ID: {}, 用户名: {}", savedUser.getId(), savedUser.getUsername());
        
        // 分配角色（如果提供了角色ID）
        if (userDto.getRoleIds() != null && userDto.getRoleIds().length > 0) {
            log.info("为新用户分配角色 - 用户ID: {}, 角色数量: {}", savedUser.getId(), userDto.getRoleIds().length);
            assignRoles(savedUser.getId(), userDto.getRoleIds());
        }
        
        return convertToDto(savedUser);
    }

    /**
     * 更新用户信息
     * 会检查用户名和邮箱的唯一性（排除当前用户）
     * 
     * @param id 用户ID
     * @param userDto 更新的用户信息
     * @return 更新后的用户信息
     * @throws BusinessException 用户不存在或用户名/邮箱重复时抛出
     */
    @Transactional
    public UserDto updateUser(Long id, UserDto userDto) {
        log.info("开始更新用户 - ID: {}", id);
        
        User user = userRepository.findById(id)
            .orElseThrow(() -> new BusinessException("用户不存在"));
        
        // 检查用户名是否重复（排除当前用户）
        if (!user.getUsername().equals(userDto.getUsername()) && 
            userRepository.existsByUsername(userDto.getUsername())) {
            throw new BusinessException("用户名已存在");
        }
        
        // 检查邮箱是否重复（排除当前用户）
        if (!user.getEmail().equals(userDto.getEmail()) && 
            userRepository.existsByEmail(userDto.getEmail())) {
            throw new BusinessException("邮箱已被使用");
        }
        
        // 更新用户信息
        user.setUsername(userDto.getUsername());
        user.setEmail(userDto.getEmail());
        user.setFullName(userDto.getFullName());
        user.setPhoneNumber(userDto.getPhoneNumber());
        user.setEnabled(userDto.getEnabled());
        
        // 如果提供了新密码，则更新密码
        if (userDto.getPassword() != null && !userDto.getPassword().isEmpty()) {
            log.info("更新用户密码 - ID: {}", id);
            user.setPassword(passwordEncoder.encode(userDto.getPassword()));
        }
        
        User savedUser = userRepository.save(user);
        log.info("用户信息更新成功 - ID: {}", id);
        
        // 更新角色分配
        if (userDto.getRoleIds() != null) {
            log.info("更新用户角色 - 用户ID: {}, 角色数量: {}", id, userDto.getRoleIds().length);
            assignRoles(savedUser.getId(), userDto.getRoleIds());
        }
        
        return convertToDto(savedUser);
    }

    /**
     * 删除用户
     * 会同时删除用户的角色关联关系
     * 
     * @param id 用户ID
     * @throws BusinessException 用户不存在时抛出
     */
    @Transactional
    public void deleteUser(Long id) {
        log.info("开始删除用户 - ID: {}", id);
        
        User user = userRepository.findById(id)
            .orElseThrow(() -> new BusinessException("用户不存在"));
        
        // 删除用户角色关联
        log.debug("删除用户角色关联 - 用户ID: {}", id);
        userRoleRepository.deleteByUserId(id);
        
        // 删除用户
        userRepository.delete(user);
        log.info("用户删除成功 - ID: {}, 用户名: {}", id, user.getUsername());
    }

    /**
     * 更新用户启用状态
     * 
     * @param id 用户ID
     * @param enabled 启用状态（true=启用，false=禁用）
     * @throws BusinessException 用户不存在时抛出
     */
    public void updateUserStatus(Long id, Boolean enabled) {
        log.info("更新用户状态 - ID: {}, 状态: {}", id, enabled ? "启用" : "禁用");
        
        User user = userRepository.findById(id)
            .orElseThrow(() -> new BusinessException("用户不存在"));
        
        user.setEnabled(enabled);
        userRepository.save(user);
        
        log.info("用户状态更新成功 - ID: {}, 新状态: {}", id, enabled ? "启用" : "禁用");
    }

    /**
     * 重置用户密码
     * 新密码会进行加密处理
     * 
     * @param id 用户ID
     * @param newPassword 新密码（明文）
     * @throws BusinessException 用户不存在时抛出
     */
    public void resetPassword(Long id, String newPassword) {
        log.info("重置用户密码 - ID: {}", id);
        
        User user = userRepository.findById(id)
            .orElseThrow(() -> new BusinessException("用户不存在"));
        
        // 加密新密码
        user.setPassword(passwordEncoder.encode(newPassword));
        userRepository.save(user);
        
        log.info("用户密码重置成功 - ID: {}", id);
    }

    /**
     * 为用户分配角色
     * 会先删除用户现有的所有角色，然后分配新的角色
     * 
     * @param userId 用户ID
     * @param roleIds 角色ID数组（可为空或空数组）
     */
    @Transactional
    public void assignRoles(Long userId, Long[] roleIds) {
        log.info("为用户分配角色 - 用户ID: {}, 角色数量: {}", userId, roleIds != null ? roleIds.length : 0);
        
        // 删除用户现有的所有角色
        userRoleRepository.deleteByUserId(userId);
        log.debug("已删除用户现有角色 - 用户ID: {}", userId);
        
        // 分配新角色
        if (roleIds != null && roleIds.length > 0) {
            List<UserRole> userRoles = Arrays.stream(roleIds)
                .map(roleId -> {
                    UserRole userRole = new UserRole();
                    userRole.setUserId(userId);
                    userRole.setRoleId(roleId);
                    return userRole;
                })
                .collect(Collectors.toList());
            
            userRoleRepository.saveAll(userRoles);
            log.info("角色分配成功 - 用户ID: {}, 分配角色数量: {}", userId, roleIds.length);
        } else {
            log.info("未分配任何角色 - 用户ID: {}", userId);
        }
    }

    /**
     * 将User实体转换为UserDto
     * 包含用户基本信息和关联的角色信息
     * 
     * @param user 用户实体
     * @return 用户DTO
     */
    private UserDto convertToDto(User user) {
        UserDto dto = new UserDto();
        dto.setId(user.getId());
        dto.setUsername(user.getUsername());
        dto.setEmail(user.getEmail());
        dto.setFullName(user.getFullName());
        dto.setPhoneNumber(user.getPhoneNumber());
        dto.setEnabled(user.getEnabled());
        dto.setAccountLocked(user.getAccountLocked());
        dto.setLastLoginAt(user.getLastLoginAt());
        dto.setCreatedAt(user.getCreatedAt());
        dto.setUpdatedAt(user.getUpdatedAt());
        
        // 获取用户角色信息
        List<Long> roleIds = userRoleRepository.findRoleIdsByUserId(user.getId());
        if (!roleIds.isEmpty()) {
            List<Role> roles = roleRepository.findAllById(roleIds);
            dto.setRoles(roles.stream().map(Role::getName).collect(Collectors.toSet()));
            dto.setDisplayName(roles.stream().map(Role::getDisplayName).collect(Collectors.joining(", ")));
            dto.setRoleIds(roleIds.toArray(new Long[0]));
        }
        
        return dto;
    }
}
